// TODO: CODE REVIEW & CLEANUP!
//
// MusicController.cs
//
// Implements MusicController and related types.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Xml;
using HomeUX.ClientAppSupport;
using HomeUX.ClientControlSupport;
using HomeUX.Controls;
using HomeUX.Synchronization;
using HomeUX.Utilities;
using SyncPathClass = HomeUX.Synchronization.SyncPath;

namespace HomeUX.MusicClientLibrary
{

[TemplatePart(Name = LayoutRootName, Type = typeof(ContentControl))]
[TemplatePart(Name = NoAlbumArtIconName, Type = typeof(NoAlbumArtIcon))]
[TemplatePart(Name = AlbumArtImageName, Type = typeof(Image))]
[TemplatePart(Name = ArtistNameTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = AlbumNameTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = TrackNameTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = ComposerTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = YearTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = DurationTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = BrowseArtistsButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = SearchButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = ViewThisArtistButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = ViewThisAlbumButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = ViewNowPlayingAlbumButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = StopButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = PreviousTrackButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = NextTrackButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = PauseButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = RewindButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = PlayButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = FastForwardButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = QueueStateTextBlockName, Type = typeof(TextBlock))]
[TemplatePart(Name = ViewQueueButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = AddToQueueButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = ClearQueueButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = ExitAddToQueueModeButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = AddToQueueButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = NoMediaControlsMessageName, Type = typeof(UIElement))]
[TemplateVisualState(GroupName = WaitingStatesName, Name = IdleStateName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = LoadingStateName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = MusicServerNotAvailableName)]
[TemplateVisualState(GroupName = MediaControlsModeStatesName, Name = ShowMediaControlsName)]
[TemplateVisualState(GroupName = MediaControlsModeStatesName, Name = NoMediaControlsName)]
[TemplateVisualState(GroupName = MediaControlsModeStatesName, Name = AddToQueueName)]
[TemplateVisualState(GroupName = TrackInfoStatesName, Name = ShowTrackInfoName)]
[TemplateVisualState(GroupName = TrackInfoStatesName, Name = HideTrackInfoName)]
public class MusicController : RangeBase, IControlHelperConnector, IDisposable
{
    const string LayoutRootName = "LayoutRoot";
    const string NoAlbumArtIconName = "NoAlbumArtIcon";
    const string AlbumArtImageName = "AlbumArtImage";
    const string ArtistNameTextBlockName = "ArtistNameTextBlock";
    const string AlbumNameTextBlockName = "AlbumNameTextBlock";
    const string TrackNameTextBlockName = "TrackNameTextBlock";
    const string ComposerTextBlockName = "ComposerTextBlock";
    const string YearTextBlockName = "YearTextBlock";
    const string DurationTextBlockName = "DurationTextBlock";
    const string BrowseArtistsButtonName = "BrowseArtistsButton";
    const string SearchButtonName = "SearchButton";
    const string ViewThisArtistButtonName = "ViewThisArtistButton";
    const string ViewThisAlbumButtonName = "ViewThisAlbumButton";
    const string ViewNowPlayingAlbumButtonName = "ViewNowPlayingAlbumButton";
    const string StopButtonName = "StopButton";
    const string PreviousTrackButtonName = "PreviousTrackButton";
    const string NextTrackButtonName = "NextTrackButton";
    const string PauseButtonName = "PauseButton";
    const string RewindButtonName = "RewindButton";
    const string PlayButtonName = "PlayButton";
    const string FastForwardButtonName = "FastForwardButton";
    const string QueueStateTextBlockName = "QueueStateTextBlock";
    const string ViewQueueButtonName = "ViewQueueButton";
    const string AddToQueueButtonName = "AddToQueueButton";
    const string NoMediaControlsMessageName = "NoMediaControlsMessage";

    const string WaitingStatesName = "WaitingStates";
    const string IdleStateName = "Idle";
    const string LoadingStateName = "Loading";
    const string MusicServerNotAvailableName = "MusicServerNotAvailable";

    const string MediaControlsModeStatesName = "MediaControlsModeStates";
    const string ShowMediaControlsName = "ShowMediaControls";
    const string NoMediaControlsName = "NoMediaControls";
    const string AddToQueueName = "AddToQueue";

    const string QueueAllTracksButtonName = "QueueAllTracksButton";
    const string ClearQueueButtonName = "ClearQueueButton";
    const string ExitAddToQueueModeButtonName = "ExitAddToQueueModeButton";

    const string TrackInfoStatesName = "TrackInfoStates";
    const string ShowTrackInfoName = "ShowTrackInfo";
    const string HideTrackInfoName = "HideTrackInfo";

    public static readonly DependencyProperty IsNowPlayingModeProperty =
        DependencyProperty.Register("IsNowPlayingMode", typeof(bool), typeof(MusicController),
            new PropertyMetadata(MusicController.OnIsNowPlayingModePropertyChanged));

    public static readonly DependencyProperty IsAddToQueueModeProperty =
        DependencyProperty.Register("IsAddToQueueMode", typeof(bool), typeof(MusicController),
            new PropertyMetadata(MusicController.OnIsAddToQueueModePropertyChanged));

    public static readonly DependencyProperty OverrideAlbumIdProperty =
        DependencyProperty.Register("OverrideAlbumId", typeof(string), typeof(MusicController),
            new PropertyMetadata(MusicController.OnOverrideAlbumIdPropertyChanged));

    public static readonly DependencyProperty OverrideArtistNameProperty =
        DependencyProperty.Register("OverrideArtistName", typeof(string), typeof(MusicController),
            new PropertyMetadata(MusicController.OnOverrideArtistNamePropertyChanged));

    public static readonly DependencyProperty OverrideAlbumNameProperty =
        DependencyProperty.Register("OverrideAlbumName", typeof(string), typeof(MusicController),
            new PropertyMetadata(MusicController.OnOverrideAlbumNamePropertyChanged));

    public static readonly DependencyProperty OverrideAlbumArtProperty =
        DependencyProperty.Register("OverrideAlbumArt", typeof(byte[]), typeof(MusicController),
            new PropertyMetadata(MusicController.OnOverrideAlbumArtPropertyChanged));

    public static readonly DependencyProperty SyncPathProperty =
        DependencyProperty.Register("SyncPath", typeof(string), typeof(MusicController),
            new PropertyMetadata(MusicController.OnSyncPathPropertyChanged));

	ContentControl _layoutRoot;
	NoAlbumArtIcon _noAlbumArtIcon;
	Image _albumArtImage;
	TextBlock _artistNameTextBlock;
	TextBlock _albumNameTextBlock;
    TextBlock _trackNameTextBlock;
    TextBlock _composerTextBlock;
	TextBlock _yearTextBlock;
	TextBlock _durationTextBlock;
	TouchButton _browseArtistsButton;
	TouchButton _searchButton;
	TouchButton _viewThisArtistButton;
	TouchButton _viewThisAlbumButton;
    TouchButton _viewNowPlayingAlbumButton;
    TouchButton _stopButton;
	TouchButton _previousTrackButton;
	TouchButton _nextTrackButton;
	TouchButton _pauseButton;
	TouchButton _rewindButton;
	TouchButton _playButton;
	TouchButton _fastForwardButton;
	TextBlock _queueStateTextBlock;
	TouchButton _viewQueueButton;
    TouchButton _addToQueueButton;
    UIElement _noMediaControlsMessage;
    TouchButton _queueAllTracksButton;
    TouchButton _clearQueueButton;
    TouchButton _exitAddToQueueModeButton;

    public event EventHandler BrowseArtistsButtonClick;
    public event EventHandler SearchButtonClick;
    public event EventHandler ViewThisArtistButtonClick;
    public event EventHandler ViewThisAlbumButtonClick;
    public event EventHandler ViewNowPlayingButtonClick;
    public event EventHandler ViewQueueButtonClick;
    //public event EventHandler AddToQueueButtonClick;
    public event EventHandler IsAddToQueueModeChange;
    public event EventHandler BasicStatusChange;

	/// <summary>
	/// Fired when the containing screen should navigate to the library screen that displays
	/// <r>CurrentAlbum</r>, e.g. because the new album began playing and the current state of
	/// <r>MusicController</r> is such that the containing screen should be "following the music".
	/// When the switch to the new album is complete, the containing screen must call
    /// <r>UpdateDisplay</r>.
	/// </summary>
    public event EventHandler MustNavigateToNewAlbum;

    public event EventHandler QueueAllTracksButtonClick;
    public event EventHandler ClearQueueButtonClick;

    /// <summary>
    /// Once the control's <r>SyncPath</r> property has been set to a non-<n>null</n> value,
    /// <r>_syncPath</r> is set to that value.  After that, for this control, it's illegal to
    /// change <r>SyncPath</r> to a value other than <r>_syncPath</r>.
    /// </summary>
    SyncPath _syncPath;

    Guid _recipientId;

    SyncNode _musicPlayerNode; // "<device>" node
    SyncNode _playStateNode; // "<device>\PlayState" node
    SyncNode _albumIdNode; // "<device>\AlbumId" node
    SyncNode _trackNumberNode; // "<device>\TrackNumber" node
    SyncNode _positionNode; // "<device>\Position" node
    SyncNode _queueIndexNode; // "<device>\QueueIndex" node
    SyncNode _queueSummaryNode; // "<device>\QueueSummary" node

    List<Action> _waitingOnMusicPlayerNode = new List<Action>();

    /// <summary>
	/// Returns <n>true</n> if the transport controls (e.g. Play and Pause) can be displayed.
    /// </summary>
	///
    /// <remarks>
	/// <para>
    /// If <r>CanControlDisplayedAlbum</r> is <n>true</n>, it makes logical sense to display the
	/// transport controls (e.g. Play and Pause) because there's a track to control, either
	/// because we're in "now-playing mode" (in which case we always show the transport controls,
	/// though they may be disabled if there's no current album) or because there's no "current
	/// track" (in which case at least Play makes sense, i.e. start playing the displayed album,
	/// i.e. the one specified by <r>OverrideAlbumId</r>), or because the displayed album
	/// (<r>OverrideAlbumIde</r>) is the same as the "current track" at the music server.
	/// </para>
	/// <para>
    /// Note that, in "library mode" (i.e. <r>_musicServerStatus.PlayState</r> is
	/// <r>PlayState.NoCurrentTrack</r>), if another album is playing, showing the transport
	/// controls -- even disabled -- would be confusing, because the user isn't (or wouldn't be, if
	/// enabled) controlling the album they're looking at.  That's why
	/// <r>CanControlDisplayedAlbum</r> is <n>false</n> in that situation.
	/// </para>
	/// <para>
	/// Also note that <r>CanControlDisplayedAlbum</r> is not affected by
	/// <r>IsCurrentDetailedStatus</r>.  If we don't have up-to-date detailed status, then the UI
	/// should be indicating that we're trying to retrieve that status, and it probably doesn't
	/// make sense to display the "another album is current" message when in fact we don't know if
	/// that's the case.
	/// </para>
    /// </remarks>
	///
    bool CanControlDisplayedAlbum
    {
        get
        {
            return IsNowPlayingMode ||
                (_musicServerStatus.PlayState == PlayState.NoCurrentTrack) ||
                (_musicServerStatus.AlbumId == OverrideAlbumId);
        }
    }

    LoadState _loadState = LoadState.WaitingForServerAvailability;

    /// <summary>
    /// The status of the music server.  Will not be <n>null</n>.
    /// </summary>
    /// <remarks>
    /// Note that <r>_musicServerStatus</r> can temporarily contain inconsistent
	/// information: for example, if the queue contains track A from album X and track B from
    /// album Y, and A just finished playing, then when B starts playing <r>_musicServerStatus</r>
	/// will contain (e.g.) album art for X and track name for A but the current-position indicator
	/// (e.g. "0:42 / 2:50") of B.  When this situation is detected,
	/// <r>TryRequestDetailedStatus</r> is called to request up-to-date <r>DetailedStatus</r>, and
    /// <r>IsCurrentDetailedStatus</r> will be <n>false</n> until updated <r>DetailedStatus</r> is
    /// received.
    /// </remarks>
    DetailedStatus _musicServerStatus = new DetailedStatus();

    /// <summary>
    /// The name of the artist of the track that's "current" at the music server, or <n>null</n> if
	/// no track is "current" at the music server.  Note that if <r>IsNowPlayingMode</r> is
	/// <n>false</n>, <r>OverrideArtistName</r> is what's displayed to the user by this control --
	/// not <r>CurrentArtistName</r>.
    /// </summary>
    public string CurrentArtistName
    {
        get
        {
            if (_musicServerStatus.PlayState == PlayState.NoCurrentTrack)
                return null;
            else
                return _musicServerStatus.ArtistName;
        }
    }

    /// <summary>
    /// The ID of the album of the track that's "current" at the music server, or <n>null</n> if no
	/// track is "current" at the music server.  Note that if <r>IsNowPlayingMode</r> is
	/// <n>false</n>, <r>OverrideAlbumId</r> is the ID of the album that's displayed to the user by
	/// this control -- not <r>CurrentAlbumId</r>.
    /// </summary>
    public string CurrentAlbumId
    {
        get
        {
            if (_musicServerStatus.PlayState == PlayState.NoCurrentTrack)
                return null;
            else
                return _musicServerStatus.AlbumId;
        }
    }

    /// <summary>
    /// The track number of the track that's "current" at the music server, or <n>-1</n> if no
	/// track is "current" at the music server.  Note that if <r>IsNowPlayingMode</r> is
	/// <n>false</n>, <r>CurrentTrackNumber</r> is not displayed to the user by this control.
    /// </summary>
    public int CurrentTrackNumber
    {
        get
        {
            if (_musicServerStatus.PlayState == PlayState.NoCurrentTrack)
                return -1;
            else
                return _musicServerStatus.TrackNumber;
        }
    }

    /// <summary>
    /// If <n>true</n>, we're in "now playing" mode, which means that this control displays
    /// the album that's currently playing on the music server.  If <n>false</n>, we're in
    /// "library mode", which means that this control displays the album specified by
    /// <r>OverrideArtistName</r>, <r>OverrideAlbumName</r>, and <r>OverrideAlbumArt</r>.
	/// (In the latter case, <r>OverrideAlbumId</r> is used to help figure out if the album
	/// currently playing on the music server is the same as the album being displayed, which will
	/// affect the UI.)
    /// </summary>
    public bool IsNowPlayingMode
    {
        get
        {
            return (bool)GetValue(IsNowPlayingModeProperty);
        }
        set
        {
            SetValue(IsNowPlayingModeProperty, value);
        }
    }

    public bool IsAddToQueueMode
    {
        get
        {
            return (bool)GetValue(IsAddToQueueModeProperty);
        }
        set
        {
            SetValue(IsAddToQueueModeProperty, value);
        }
    }

    /// <summary>
    /// The album ID of the album specified by the <r>OverrideArtistName</r>,
    /// <r>OverrideAlbumName</r>, and <r>OverrideAlbumArt</r> properties, or <n>null</n> if those
    /// properties are <n>null</n> (indicating that no album is to be displayed).
    /// Ignored unless we're in "library mode"
    /// (i.e. <r>IsNowPlayingMode</r> is <n>false</n>) -- otherwise, the album that's currently
    /// playing on the music server is displayed.
    /// </summary>
	///
	/// <remarks>
	/// <r>OverrideAlbumId</r> is used in "library mode" to help figure out if the album currently
	/// playing on the music server is the same as the album being displayed (as specified by the
	/// other <n>Override*</r> properties).  This affects the user interface: for example, if the
	/// displayed album is the one that's being played, the user is given UI elements to control
	/// playback of the album.
	/// </remarks>
	///
    public string OverrideAlbumId
    {
        get
        {
            return (string)GetValue(OverrideAlbumIdProperty);
        }
        set
        {
            SetValue(OverrideAlbumIdProperty, value);
        }
    }

    /// <summary>
    /// The name of the artist to display, or <n>null</n> if none.  Ignored unless we're in "library mode"
    /// (i.e. <r>IsNowPlayingMode</r> is <n>false</n>) -- otherwise, the album that's currently
    /// playing on the music server is displayed.
    /// </summary>
    public string OverrideArtistName
    {
        get
        {
            return (string)GetValue(OverrideArtistNameProperty);
        }
        set
        {
            SetValue(OverrideArtistNameProperty, value);
        }
    }

    /// <summary>
    /// The name of the album to display, or <n>null</n> if none.  Ignored unless we're in "library mode"
    /// (i.e. <r>IsNowPlayingMode</r> is <n>false</n>) -- otherwise, the album that's currently
    /// playing on the music server is displayed.
    /// </summary>

    public string OverrideAlbumName
    {
        get
        {
            return (string)GetValue(OverrideAlbumNameProperty);
        }
        set
        {
            SetValue(OverrideAlbumNameProperty, value);
        }
    }

    /// <summary>
    /// The bytes of the album art image to display, or <n>null</n> to display the "no album art"
    /// image.  Ignored unless we're in "library mode"
    /// (i.e. <r>IsNowPlayingMode</r> is <n>false</n>) -- otherwise, the album that's currently
    /// playing on the music server is displayed.
    /// </summary>
    public byte[] OverrideAlbumArt
    {
        get
        {
            return (byte[])GetValue(OverrideAlbumArtProperty);
        }
        set
        {
            SetValue(OverrideAlbumArtProperty, value);
        }
    }

    public string SyncPath
    {
        get
        {
            return (string)GetValue(SyncPathProperty);
        }
        set
        {
            SetValue(SyncPathProperty, value);
        }
    }

    public bool QueueAllTracksButtonIsWaiting
    {
        get
        {
            return _queueAllTracksButton.IsWaiting;
        }
        set
        {
            _queueAllTracksButton.IsWaiting = value;
        }
    }

    public bool ClearQueueButtonIsWaiting
    {
        get
        {
            return _clearQueueButton.IsWaiting;
        }
        set
        {
            _clearQueueButton.IsWaiting = value;
        }
    }

    public MusicController() 
    {
        DefaultStyleKey = typeof(MusicController);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

		ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
		ControlHelper.GetRequiredTemplateChild(this, NoAlbumArtIconName, out _noAlbumArtIcon);
		ControlHelper.GetRequiredTemplateChild(this, AlbumArtImageName, out _albumArtImage);
		ControlHelper.GetRequiredTemplateChild(this, ArtistNameTextBlockName,
			out _artistNameTextBlock);
		ControlHelper.GetRequiredTemplateChild(this, AlbumNameTextBlockName,
			out _albumNameTextBlock);
        ControlHelper.GetRequiredTemplateChild(this, TrackNameTextBlockName,
            out _trackNameTextBlock);
        ControlHelper.GetRequiredTemplateChild(this, ComposerTextBlockName, out _composerTextBlock);
		ControlHelper.GetRequiredTemplateChild(this, YearTextBlockName, out _yearTextBlock);
		ControlHelper.GetRequiredTemplateChild(this, DurationTextBlockName, out _durationTextBlock);
		ControlHelper.GetRequiredTemplateChild(this, BrowseArtistsButtonName,
			out _browseArtistsButton);
		ControlHelper.GetRequiredTemplateChild(this, SearchButtonName, out _searchButton);
		ControlHelper.GetRequiredTemplateChild(this, ViewThisArtistButtonName,
			out _viewThisArtistButton);
		ControlHelper.GetRequiredTemplateChild(this, ViewThisAlbumButtonName,
            out _viewThisAlbumButton);
        ControlHelper.GetRequiredTemplateChild(this, ViewNowPlayingAlbumButtonName,
            out _viewNowPlayingAlbumButton);
        ControlHelper.GetRequiredTemplateChild(this, StopButtonName, out _stopButton);
		ControlHelper.GetRequiredTemplateChild(this, PreviousTrackButtonName,
			out _previousTrackButton);
		ControlHelper.GetRequiredTemplateChild(this, NextTrackButtonName, out _nextTrackButton);
		ControlHelper.GetRequiredTemplateChild(this, PauseButtonName, out _pauseButton);
		ControlHelper.GetRequiredTemplateChild(this, RewindButtonName, out _rewindButton);
		ControlHelper.GetRequiredTemplateChild(this, PlayButtonName, out _playButton);
		ControlHelper.GetRequiredTemplateChild(this, FastForwardButtonName, out _fastForwardButton);
		ControlHelper.GetRequiredTemplateChild(this, QueueStateTextBlockName,
			out _queueStateTextBlock);
		ControlHelper.GetRequiredTemplateChild(this, ViewQueueButtonName, out _viewQueueButton);
        ControlHelper.GetRequiredTemplateChild(this, AddToQueueButtonName, out _addToQueueButton);
        ControlHelper.GetRequiredTemplateChild(this, NoMediaControlsMessageName, out _noMediaControlsMessage);
        ControlHelper.GetRequiredTemplateChild(this, QueueAllTracksButtonName, out _queueAllTracksButton);
        ControlHelper.GetRequiredTemplateChild(this, ClearQueueButtonName, out _clearQueueButton);
        ControlHelper.GetRequiredTemplateChild(this, ExitAddToQueueModeButtonName, out _exitAddToQueueModeButton);

		_browseArtistsButton.Click += _browseArtistsButton_Click;
        _albumArtImage.MouseLeftButtonDown += _albumArtImage_MouseLeftButtonDown;
        _noAlbumArtIcon.MouseLeftButtonDown += _albumArtImage_MouseLeftButtonDown;
		_searchButton.Click += _searchButton_Click;
		_viewThisArtistButton.Click += _viewThisArtistButton_Click;
		_viewThisAlbumButton.Click += _viewThisAlbumButton_Click;
		_viewNowPlayingAlbumButton.Click += _viewNowPlayingAlbumButton_Click;
		_stopButton.Click += _stopButton_Click;
		_previousTrackButton.Click += _previousTrackButton_Click;
		_nextTrackButton.Click += _nextTrackButton_Click;
		_pauseButton.Click += _pauseButton_Click;
		_rewindButton.Click += _rewindButton_Click;
		_playButton.Click += _playButton_Click;
		_fastForwardButton.Click += _fastForwardButton_Click;
		_viewQueueButton.Click += _viewQueueButton_Click;
		_addToQueueButton.Click += _addToQueueButton_Click;
        _noMediaControlsMessage.MouseLeftButtonDown += _noMediaControlsMessage_MouseLeftButtonDown;
        _queueAllTracksButton.Click += _queueAllTracksButton_Click;
        _clearQueueButton.Click += _clearQueueButton_Click;
        _exitAddToQueueModeButton.Click += _exitAddToQueueModeButton_Click;

        OnIsNowPlayingModePropertyChanged();

        var syncClient = ClientGlobals.GetSyncClient(this) as SyncClient;
        if (syncClient != null) // i.e. if run time, not design time
            _recipientId = syncClient.RegisterParcelRecipient(OnParcelReceived);

        TryRequestDetailedStatus(false);

        // initialize UI based on initial property values
        UpdateVisualState(false);
    }

	void _browseArtistsButton_Click(object sender, RoutedEventArgs e)
	{
		// fire the BrowseArtistsButtonClick event
        if (BrowseArtistsButtonClick != null)
            BrowseArtistsButtonClick(this, EventArgs.Empty);
	}

    void _albumArtImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (_viewThisAlbumButton.IsEnabled)
        {
			// fire the ViewThisAlbumButtonClick event
            if (ViewThisAlbumButtonClick != null)
                ViewThisAlbumButtonClick(this, EventArgs.Empty);
        }
    }

	void _searchButton_Click(object sender, RoutedEventArgs e)
    {
		// fire the SearchButtonClick event
        if (SearchButtonClick != null)
            SearchButtonClick(this, EventArgs.Empty);
    }

	void _viewThisArtistButton_Click(object sender, RoutedEventArgs e)
	{
		// fire the ViewThisArtistButtonClick event
        if (ViewThisArtistButtonClick != null)
            ViewThisArtistButtonClick(this, EventArgs.Empty);
    }

	void _viewThisAlbumButton_Click(object sender, RoutedEventArgs e)
	{
		// fire the ViewThisAlbumButtonClick event
        if (ViewThisAlbumButtonClick != null)
            ViewThisAlbumButtonClick(this, EventArgs.Empty);
    }

	void _viewNowPlayingAlbumButton_Click(object sender, RoutedEventArgs e)
	{
		// fire the ViewNowPlayingButtonClick event
        if (ViewNowPlayingButtonClick != null)
            ViewNowPlayingButtonClick(this, EventArgs.Empty);
    }

	void _stopButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
        {
            MakeButtonWaitForMusicPlayerNode(_stopButton);
            _musicPlayerNode.ExecuteCommand("Stop");
        }
    }

	void _previousTrackButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
        {
            MakeButtonWaitForMusicPlayerNode(_previousTrackButton);
            _musicPlayerNode.ExecuteCommand("PreviousTrack");
        }
    }

	void _nextTrackButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
        {
            MakeButtonWaitForMusicPlayerNode(_nextTrackButton);
            _musicPlayerNode.ExecuteCommand("NextTrack");
        }
    }

	void _pauseButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
        {
            MakeButtonWaitForMusicPlayerNode(_pauseButton);
			if (_musicServerStatus.PlayState == PlayState.Paused)
				_musicPlayerNode.ExecuteCommand("Play");
			else
				_musicPlayerNode.ExecuteCommand("Pause");
        }
    }

	void _rewindButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
		{
			MakeButtonWaitForMusicPlayerNode(_rewindButton);
			if (_musicServerStatus.PlayState == PlayState.ScanReverse)
				_musicPlayerNode.ExecuteCommand("Play");
			else
				_musicPlayerNode.ExecuteCommand("FastReverse");
		}
    }

	void _playButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
		{
            switch (_musicServerStatus.PlayState)
			{

			case PlayState.NoCurrentTrack:

				// there's no current track (i.e. the queue is empty) -- in this case, if we're in
				// "library mode" and an "override album" is specified (i.e. the album the user is
				// looking at), we start playing that album from the beginning
				if (!IsNowPlayingMode && !String.IsNullOrEmpty(OverrideAlbumId))
				{
					string command = ParsedSyncNodeCommand.Format("PlayAlbum",
						"RecipientId", XmlConvert.ToString(_recipientId),
						"AlbumId", OverrideAlbumId,
						"StartAtTrackNumber", "-1"/*"from the beginning"*/);
					MakeButtonWaitForMusicPlayerNode(_playButton);
					_musicPlayerNode.ExecuteCommand(command);
				}
				break;

			case PlayState.Playing:

				MakeButtonWaitForMusicPlayerNode(_playButton);
				_musicPlayerNode.ExecuteCommand("Pause");
				break;

			default:

				MakeButtonWaitForMusicPlayerNode(_playButton);
                _musicPlayerNode.ExecuteCommand("Play");
				break;
			}
        }
    }

    void MakeButtonWaitForMusicPlayerNode(TouchButton button)
    {
        button.IsWaiting = true;
        WaitForMusicPlayerNode(delegate
        {
            button.IsWaiting = false;
        });
    }

	void _fastForwardButton_Click(object sender, RoutedEventArgs e)
	{
        if (_musicPlayerNode != null)
        {
            MakeButtonWaitForMusicPlayerNode(_fastForwardButton);
            if (_musicServerStatus.PlayState == PlayState.ScanForward)
                _musicPlayerNode.ExecuteCommand("Play");
            else
                _musicPlayerNode.ExecuteCommand("FastForward");
        }
    }

	void _viewQueueButton_Click(object sender, RoutedEventArgs e)
	{
		// fire the ViewQueueButtonClick event
        if (ViewQueueButtonClick != null)
            ViewQueueButtonClick(this, EventArgs.Empty);
    }

	void _addToQueueButton_Click(object sender, RoutedEventArgs e)
	{
        IsAddToQueueMode = !IsAddToQueueMode;
        
        /*
        // fire the AddToQueueButtonClick event
        if (AddToQueueButtonClick != null)
            AddToQueueButtonClick(this, EventArgs.Empty);
       */
	}

    void _noMediaControlsMessage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
		// fire the ViewNowPlayingButtonClick event
        if (ViewNowPlayingButtonClick != null)
            ViewNowPlayingButtonClick(this, EventArgs.Empty);
    }

    void _queueAllTracksButton_Click(object sender, RoutedEventArgs e)
    {
		// fire the QueueAllTracksButtonClick event
        if (QueueAllTracksButtonClick != null)
            QueueAllTracksButtonClick(this, EventArgs.Empty);
    }

    void _clearQueueButton_Click(object sender, RoutedEventArgs e)
    {
		// fire the ClearQueueButtonClick event
        if (ClearQueueButtonClick != null)
            ClearQueueButtonClick(this, EventArgs.Empty);
    }

    void _exitAddToQueueModeButton_Click(object sender, RoutedEventArgs e)
    {
        IsAddToQueueMode = false;
    }

    void _musicPlayerNode_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        TryRequestDetailedStatus(true);
    }

    /// <summary>
    /// Calls a given action when <r>MusicPlayerNode.IsWaiting</r> is no longer <n>true</n>.
    /// If it's currently <n>false</n>, the action will be called when it's set to <n>true</n>
    /// and then <n>false</n>.
    /// </summary>
    /// <param name="action"></param>
    public void WaitForMusicPlayerNode(Action action)
    {
        _waitingOnMusicPlayerNode.Add(action);
    }

    void _musicPlayerNode_IsWaitingChanged(object sender, IsWaitingChangedEventArgs e)
    {
        if (!_musicPlayerNode.IsWaiting)
        {
            foreach (Action action in _waitingOnMusicPlayerNode)
                action();
            _waitingOnMusicPlayerNode.Clear();
        }
    }

    void _queueSummaryNode_ValueChanged(object sender, ValueChangedEventArgs e)
    {
        // if, according to current state in <_musicServerStatus>, we haven't yet received
		// DetailedStatus for the track that's the "current track" at the music server, do nothing
		// further here -- "queue summary" can only work as a delta on top of "detailed status"
        if (!_musicServerStatus.IsCurrentDetailedStatus)
            return;

        string[] parts = ((string)e.NewValue).Split(';');
        if (parts.Length >= 2)
        {
            _musicServerStatus.QueuedTrackCount = XmlConvert.ToInt32(parts[0]);
            _musicServerStatus.QueuedTrackDuration = XmlConvert.ToInt32(parts[1]);
        }
        DisplayQueueState();

        // <_nextTrackButton> is enabled or disabled based on
        // <_musicServerStatus.QueuedTrackCount>, so we need to call the code that's
        // responsible for updating its state
        UpdateBasicUi();
    }

    /// <summary>
    /// Requests that the information displayed by this control be reflect, for example,
    /// updates to <r>OverrideAlbumId</r> and related properties.  See
    /// <r>MustNavigateToNewAlbum</r> for more information.
    /// </summary>
    public void UpdateDisplay()
    {
        TryRequestDetailedStatus(true);
    }

    bool _isRequestingDetailedStatus;

    /// <summary>
    /// Requests that the currently-displayed information be updated to reflect what the
    /// currently-playing album and track is.  Normally this happens automatically; however,
    /// he containing screen should call this method after <r>MustNavigateToNewAlbum</r> has been
    /// fired and the switch to the new album is complete.
    /// </summary>
    void TryRequestDetailedStatus(bool useTransitions)
    {
        // record the fact that we now consider <_musicServerStatus> to no longer be up-to-date
        _musicServerStatus.IsCurrentDetailedStatus = false;

        // do nothing further if we've already begun requesting detailed status
        if (_isRequestingDetailedStatus)
            return;

        // if we're in a state where we can perform a "GetDetailedStatus" request, do so
        if ((_layoutRoot != null) &&
            (_musicPlayerNode != null) && (_musicPlayerNode.Value != null) &&
		    (_loadState != LoadState.Loading))
        {
            _isRequestingDetailedStatus = true;
            string command = ParsedSyncNodeCommand.Format("GetDetailedStatus",
                "RecipientId", XmlConvert.ToString(_recipientId));
            _musicPlayerNode.ExecuteCommand(command);
            _loadState = LoadState.Loading;
            UpdateVisualState(useTransitions);
        }
    }

    void OnParcelReceived(Guid recipientId, XmlReader reader)
    {
        DetailedStatus status = new DetailedStatus();
        //PlayState playState = PlayState.Unknown;
        //string albumId = null;
        //double position = 0;
        //double queueIndex = 0;
        //byte[] albumArtBytes = null;

        try
        {
            foreach (XmlElementInfo element in XmlElementInfo.EnumerateElements(reader))
            {
                switch (element.Name)
                {

                case "DetailedStatus":

                    _isRequestingDetailedStatus = false;
                    status.PlayState = element.GetRequiredEnumAttribute<PlayState>("PlayState");
                    status.AlbumId = element.GetRequiredAttribute("AlbumId");
                    status.TrackNumber = element.GetRequiredIntAttribute("TrackNumber");
                    status.Position = element.GetRequiredDoubleAttribute("Position");
                    status.QueueIndex = element.GetRequiredIntAttribute("QueueIndex");
					status.ArtistName = element.GetRequiredAttribute("ArtistName");
					status.AlbumName = element.GetRequiredAttribute("AlbumName");
                    status.TrackName = element.GetRequiredAttribute("TrackName");
					status.TrackDuration = element.GetRequiredDoubleAttribute("TrackDuration");
					status.TrackYear = element.GetRequiredIntAttribute("TrackYear");
					status.TrackComposer = element.GetRequiredAttribute("TrackComposer");
					status.QueuedTrackCount = element.GetRequiredIntAttribute("QueuedTrackCount");
					status.QueuedTrackDuration = element.GetRequiredDoubleAttribute(
						"QueuedTrackDuration");
                    status.Volume = element.GetRequiredIntAttribute("Volume");
                    status.IsMuted = element. GetRequiredBoolAttribute("IsMuted");
                    status.PlaybackRate = element.GetRequiredIntAttribute("PlaybackRate");
                    status.IsLoopMode = element.GetRequiredBoolAttribute("IsLoopMode");
                    status.IsShuffleMode = element.GetRequiredBoolAttribute("IsShuffleMode");
                    break;

                case "AlbumArt":

                    status.AlbumArt = element.GetContentAsBytes();
                    break;

                case "/DetailedStatus":

                    _loadState = LoadState.Done;
                    OnDetailedStatusChanged(status);
                    UpdateVisualState(true);
                    break;
                }
            }
        }
        catch (InvalidOperationException ex)
        {
            ClientGlobals.Alert(AlertType.Warning,
                "Invalid reply from server: {0}", ex.Message);
        }
    }

    /// <summary>
    /// Updates UI elements that are in common between "now playing mode" and "library mode".
    /// Ideally, this method is called only when new album information is loaded.
    /// </summary>
    void UpdateCommonUi(string artistName, string albumName, byte[] albumArt)
    {
        SetTextBlockText(_artistNameTextBlock, artistName ?? String.Empty);

        SetTextBlockText(_albumNameTextBlock, albumName ?? String.Empty);

        if (albumArt != null)
        {
            using (var stream = new MemoryStream(albumArt))
            {
                var bitmapImage = new BitmapImage();
                bitmapImage.SetSource(stream);
                _albumArtImage.Source = bitmapImage;
            }

            _noAlbumArtIcon.Visibility = Visibility.Collapsed;
        }
        else
        {
            _albumArtImage.Source = null;
            _noAlbumArtIcon.Visibility = Visibility.Visible;
        }

        // if we're not in "now playing mode", e.g. the user is looking at an album in the music
        // library, then <_viewNowPlayingAlbumButton> lets them navigate to the album of the track
        // that's "current" at the music server provided that (a) there is a "current" track, and
        // (b) that album isn't the one currently being displayed (<OverrideAlbumId>); note that
        // if we're in "now playing mode", <_viewNowPlayingAlbumButton> is hidden and not used
        // (since, by definition, we're always displaying what's "now playing")
        if (!IsNowPlayingMode)
        {
            _viewNowPlayingAlbumButton.IsEnabled =
                (_musicServerStatus.PlayState != PlayState.NoCurrentTrack) ||
                (_musicServerStatus.AlbumId != OverrideAlbumId);
        }
    }

	void DisplayQueueState()
	{
		_queueStateTextBlock.Text = String.Format(
			((_musicServerStatus.QueuedTrackCount == 1) ? "1 track - {1}" : "{0} tracks - {1}"),
			_musicServerStatus.QueuedTrackCount,
			Util.FormatDuration(_musicServerStatus.QueuedTrackDuration));
	}

    void SetTextBlockText(TextBlock textBlock, string text)
    {
        if (!String.IsNullOrEmpty(text))
            textBlock.Text = text;
        else
            textBlock.Text = String.Empty;
    }

    //string FormatMinutesSeconds(double seconds)
    //{
    //    int secondsInt = (int)seconds;
    //    return String.Format("{0}:{1:00}", secondsInt / 60, secondsInt % 60);
    //}

    static void OnSyncPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        SyncPath newSyncPath = SyncPathClass.FromString((string)e.NewValue);
        if (System.ComponentModel.DesignerProperties.IsInDesignTool)
        {
            // in design-time mode, let <SyncPath> be set arbitrarily, but leave <_syncPath> null
        }
        else
        if (target._syncPath == null)
        {
            if (newSyncPath != null)
            {
                target._syncPath = newSyncPath;
                target.OnSyncPathSet();
            }
        }
        else
        if (newSyncPath != target._syncPath)
        {
            throw new InvalidOperationException(
                "Cannot change SyncPath of MusicController once it's been set");
        }
    }

    void OnSyncPathSet()
    {
        ISyncClient syncClient = ClientGlobals.GetSyncClient(this);

		if (_syncPath != null)
		{
			_musicPlayerNode = (SyncNode)syncClient.SyncTree.CreateNode(_syncPath);
			_playStateNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "PlayState"));
			_albumIdNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "AlbumId"));
			_trackNumberNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "TrackNumber"));
			_positionNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "Position"));
			_queueIndexNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "QueueIndex"));
			_queueSummaryNode = (SyncNode)syncClient.SyncTree.CreateNode(
				SyncPathClass.Build(_syncPath, "QueueSummary"));

			_musicPlayerNode.ValueChanged += _musicPlayerNode_ValueChanged;
			_musicPlayerNode.IsWaitingChanged += _musicPlayerNode_IsWaitingChanged;
			_queueSummaryNode.ValueChanged += _queueSummaryNode_ValueChanged;
			_playStateNode.ValueChanged += OnBasicStatusNodeValueChanged;
			_albumIdNode.ValueChanged += OnBasicStatusNodeValueChanged;
			_trackNumberNode.ValueChanged += OnBasicStatusNodeValueChanged;
			_positionNode.ValueChanged += OnBasicStatusNodeValueChanged;
			_queueIndexNode.ValueChanged += OnBasicStatusNodeValueChanged;
		}
		else
		{
			_musicPlayerNode.ValueChanged -= _musicPlayerNode_ValueChanged;
			_musicPlayerNode.IsWaitingChanged -= _musicPlayerNode_IsWaitingChanged;
			_queueSummaryNode.ValueChanged -= _queueSummaryNode_ValueChanged;
			_playStateNode.ValueChanged -= OnBasicStatusNodeValueChanged;
			_albumIdNode.ValueChanged -= OnBasicStatusNodeValueChanged;
			_trackNumberNode.ValueChanged -= OnBasicStatusNodeValueChanged;
			_positionNode.ValueChanged -= OnBasicStatusNodeValueChanged;
			_queueIndexNode.ValueChanged -= OnBasicStatusNodeValueChanged;
		}
    }

    void OnBasicStatusNodeValueChanged(object sender, ValueChangedEventArgs e)
    {
        OnBasicStatusChangedAsync();
    }

    bool _basicStatusChangedPending;

    void OnBasicStatusChangedAsync()
    {
        if (!_basicStatusChangedPending)
        {
            _basicStatusChangedPending = true;
            Dispatcher.BeginInvoke(OnBasicStatusChanged);
        }
    }

	/// <summary>
	/// The music server reported a change in one or more of the SyncNodes that encompass
	/// "basic status" (i.e. the information in <r>BasicStatus</r>).  Before this call,
	/// <r>_musicServerStatus</r> has not yet been updated with this information -- performing that
	/// update is part of what this method does.
	/// </summary>
	///
    void OnBasicStatusChanged()
    {
        _basicStatusChangedPending = false;
        OnBasicStatusChanged(new BasicStatus()
        {
			PlayState = ParsePlayState((string)_playStateNode.Value),
			AlbumId = _albumIdNode.TryGetValue<string>(null),
			TrackNumber = _trackNumberNode.TryGetValue<int>(0),
			Position = _positionNode.TryGetValue<double>(0),
			QueueIndex = _queueIndexNode.TryGetValue<int>(0)
        });
    }

	/// <summary>
	/// Switch the state of this control to reflect the fact that there is no "current track"
	/// playing on the music server.
	/// </summary>
	void ClearMusicServerStatus()
	{
        // record the fact that the status below is current
        _musicServerStatus.IsCurrentDetailedStatus = true;

        // update status
        _musicServerStatus.PlayState = PlayState.NoCurrentTrack;
        _musicServerStatus.AlbumId = null;
        _musicServerStatus.TrackNumber = 0;
        _musicServerStatus.Position = 0;
        _musicServerStatus.QueueIndex = 0;
        _musicServerStatus.ArtistName = null;
        _musicServerStatus.AlbumName = null;
        _musicServerStatus.AlbumArt = null;
        _musicServerStatus.TrackName = null;
        _musicServerStatus.TrackDuration = 0;
        _musicServerStatus.TrackYear = 0;
        _musicServerStatus.TrackComposer = null;
        _musicServerStatus.QueuedTrackCount = 0;
        _musicServerStatus.QueuedTrackDuration = 0;

        // update the UI
		UpdateFullUi();
	}

	/// <summary>
	/// The music server reported a change in one or more of the SyncNodes that encompass
	/// "detailed status" (i.e. the information in <r>DetailedStatus</r>).  Specifically,
	/// <pr>status</pr> contains the updated status.  Before this call, <r>_musicServerStatus</r>
	/// has not yet been updated with this information -- performing that update is part of what
	/// this method does.
	/// </summary>
	///
    /// <param name="status">Updated status.  Note that
    ///     <pr>status</pr>.<s>IsCurrentDetailedStatus</s> is set to <n>true</n> by this method
    ///     because the assumption is that <pr>status</pr> contains up-to-date music server
    ///     status.</param>
    ///
    void OnDetailedStatusChanged(DetailedStatus status)
    {
        status.IsCurrentDetailedStatus = true;
        _musicServerStatus = status;
        UpdateFullUiAsync();
    }

	/// <summary>
	/// The music server reported a change in one or more of the SyncNodes that encompass
	/// "basic status" (i.e. the information in <r>BasicStatus</r>).  Specifically, <pr>status</pr>
	/// contains updated basic status.  Before this call, <r>_musicServerStatus</r> has not yet
	/// been updated with this information -- performing that update is part of what this method
	/// does.
	/// </summary>
	///
    void OnBasicStatusChanged(BasicStatus status)
    {
        // if, according to current state in <_musicServerStatus>, we haven't yet received
		// DetailedStatus for the track that's the "current track" at the music server, do nothing
		// further here -- "basic status" can only work as a delta on top of "detailed status" --
		// except, if there's no active request for detailed status, make that request now
        if (!_musicServerStatus.IsCurrentDetailedStatus)
        {
            TryRequestDetailedStatus(true);
            return;
        }

		// if <status> indicates that there is no longer a "current track", switch
		// <_musicServerStatus> and the UI to match
        if (status.PlayState == PlayState.NoCurrentTrack)
        {
			ClearMusicServerStatus();

            // fire the BasicStatusChange event
            if (BasicStatusChange != null)
                BasicStatusChange(null, EventArgs.Empty);

            return;
        }

		if ((status.AlbumId != _musicServerStatus.AlbumId) ||
			(status.TrackNumber != _musicServerStatus.TrackNumber))
		{
			// we changed to a different track or a different album
			if (status.AlbumId != _musicServerStatus.AlbumId)
            {
                // we changed to a different album
                if (!IsNowPlayingMode && !IsAddToQueueMode)
                {
                    // the containing page should navigate to a screen displaying the new album,
                    // so the user can "follow the action"...

                    // update <_musicServerStatus> with information in <status>
                    status.CopyTo(_musicServerStatus);

                    // during the transition to the new album, the UI is temporarily incorrect -- it
                    // shows information about the previous album; we could clear the entire UI during
                    // the transition (i.e. call ClearMusicServerStatus), but instead, subjectively, it
                    // looks better to just clear out a subset of the UI elements
                    _trackNameTextBlock.Text = String.Empty;
                    _composerTextBlock.Text = String.Empty;
                    _yearTextBlock.Text = String.Empty;
                    _durationTextBlock.Text = String.Empty;

                    // fire the MustNavigateToNewAlbum event
                    if (MustNavigateToNewAlbum != null)
                        MustNavigateToNewAlbum(this, EventArgs.Empty);
                }
                else
                {
                    // update <_musicServerStatus> with the information that's in <status>
                    status.CopyTo(_musicServerStatus);

                    // need to request detailed status again
                    TryRequestDetailedStatus(true);
                }
            }
			else
			{
                // update <_musicServerStatus> with the information that's in <status>
                status.CopyTo(_musicServerStatus);
                
                // need to request detailed status again
				TryRequestDetailedStatus(true);
			}
		}
		else
		{
			// the currently-playing album hasn't changed...

			// update <_musicServerStatus> with the information that's in <status>
			status.CopyTo(_musicServerStatus);

			// update the UI to reflect the new information
			UpdateBasicUi();
		}

        // fire the BasicStatusChange event -- this needs to be done *after*
        // status.CopyTo(_musicServerStatus)
        if (BasicStatusChange != null)
            BasicStatusChange(null, EventArgs.Empty);
    }

	/// <summary>
	/// Update the UI elements that get updated when information in <r>BasicStatus</r> changes.
	/// </summary>
	///
	void UpdateBasicUi()
	{
		_durationTextBlock.Text = String.Format("[{0} / {1}]",
			Util.FormatDuration(_musicServerStatus.Position),
			Util.FormatDuration(_musicServerStatus.TrackDuration));

		// set <hasTrack> to true if there is a "current track" at the music server
        bool hasTrack = (_musicServerStatus.PlayState != PlayState.NoCurrentTrack);

        // <_viewThisArtistButton> and <_viewThisAlbumButton> are enabled if there's a "current
		// track", since clicking those buttons navigates to information related to the "curren
		// track"
        _viewThisArtistButton.IsEnabled = hasTrack;
        _viewThisAlbumButton.IsEnabled = hasTrack;

		// <_stopButton> is enabled as long as there's a "current track" and we're not already stopped
		_stopButton.IsEnabled = hasTrack && (_musicServerStatus.PlayState != PlayState.Stopped);

		// <_previousTrackButton> is enabled if there's a "previous track" in the queue; it's also
        // enabled in loop mode (because we can loop back around to the end of the queue) and in
        // shuffle mode (because we can't determine if there's a previous track to play based on
        // our position in the queue or any other information we have in <_musicServerStatus>)
        _previousTrackButton.IsEnabled = (_musicServerStatus.QueueIndex > 0) ||
            _musicServerStatus.IsLoopMode || _musicServerStatus.IsShuffleMode;

        // <_nextTrackButton> is enabled if there's a "next track" in the queue; it's also enabled
        // in loop mode (because we can loop back around to the beginning of the queue) and in
        // shuffle mode (because we can't determine if there's a next track to play based on
        // our position in the queue or any other information we have in <_musicServerStatus>)
        _nextTrackButton.IsEnabled = (_musicServerStatus.QueuedTrackCount > 0) ||
            _musicServerStatus.IsLoopMode || _musicServerStatus.IsShuffleMode;

        // <_pauseButton> is enabled as long as there's a "current track" and we're not stopped;
        // it's lit if we're paused
        _pauseButton.IsEnabled = hasTrack && (_musicServerStatus.PlayState != PlayState.Stopped);
        _pauseButton.IsLit = (_musicServerStatus.PlayState == PlayState.Paused);
       
		// <_rewindButton> is enabled if there's a current track and we're not stopped and we're not
        // already at the beginning of the track; it's lit if we're rewinding
        _rewindButton.IsEnabled = hasTrack &&
            (_musicServerStatus.PlayState != PlayState.Stopped) &&
            (_musicServerStatus.Position > 0);
        _rewindButton.IsLit = (_musicServerStatus.PlayState == PlayState.ScanReverse);
        
		// <_playButton> is enabled as long as there's a current track -- it's also enabled if
        // we're in "library mode" (because in that case the user is looking at an album, and
        // pressing Play when the queue is empty will start the album playing); it's lit when
        // we're playing
        _playButton.IsEnabled = hasTrack || !IsNowPlayingMode;
        _playButton.IsLit = (_musicServerStatus.PlayState == PlayState.Playing);

		// <_fastForwardButton> is enabled if there's a current track and we're not stopped; it's
        // lit if we're fast-forwarding
        _fastForwardButton.IsEnabled = hasTrack && (_musicServerStatus.PlayState != PlayState.Stopped);
        _fastForwardButton.IsLit = (_musicServerStatus.PlayState == PlayState.ScanForward);
	}

	/// <summary>
	/// Update all UI elements.
	/// </summary>
	///
    void UpdateFullUi()
    {
        _updateFullUiPending = false;

		// update the UI elements that get updated when information in BasicStatus changes
		UpdateBasicUi();

		// set <status> to be the status to display
		DetailedStatus status;
		if (IsNowPlayingMode)
		{
			// we're in "now playing" mode -- display the status of the track that's the "current track" at
            // the music server
			status = _musicServerStatus;
		}
		else
		{
            // we're in "library mode", e.g. viewing an album in the music library
            if (CanControlDisplayedAlbum)
            {
                // the album currently playing on the music server is the same as the album
                // we're currently displaying (as set by Override* properties), so we can display
                // current music server status
                status = _musicServerStatus;
            }
            else
            {
                // a different album is currently playing, so it wouldn't make sense to display
                // information like the current track -- so, much of the UI is blank, except for
                // artist name, album name, and album art
                status = new DetailedStatus();
                status.ArtistName = OverrideArtistName;
                status.AlbumName = OverrideAlbumName;
                status.AlbumArt = OverrideAlbumArt;
            }
		}

		// update artist name, album name, and album art
		UpdateCommonUi(status.ArtistName, status.AlbumName, status.AlbumArt);

		// update track name
		if (!String.IsNullOrEmpty(status.TrackName))
		{
			_trackNameTextBlock.Text = String.Format("{0}. {1}",
				status.TrackNumber, status.TrackName);
		}
		else
			_trackNameTextBlock.Text = String.Empty;

		// update composer and year
		SetTextBlockText(_composerTextBlock, status.TrackComposer);
		SetTextBlockText(_yearTextBlock,
			(status.TrackYear != 0) ? status.TrackYear.ToString() : String.Empty);

		// update UI showing the state of the queue
		DisplayQueueState();

		// update UI elements that are implemented as "visual states" in the template
        UpdateVisualState(false);
	}

    PlayState ParsePlayState(string stringValue)
    {
        switch (stringValue)
        {
        case "NoCurrentTrack":
        default:
			return PlayState.NoCurrentTrack;
        case "Stopped":
			return PlayState.Stopped;
        case "Paused":
			return PlayState.Paused;
        case "Playing":
			return PlayState.Playing;
        case "ScanForward":
            return PlayState.ScanForward;
        case "ScanReverse":
            return PlayState.ScanReverse;
        }
    }

    static void OnIsNowPlayingModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        target.OnIsNowPlayingModePropertyChanged();
    }

    static void OnIsAddToQueueModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        if (target._layoutRoot != null) // i.e. if OnApplyTemplate was called
        {
            target.UpdateVisualState(true);
            target._addToQueueButton.IsLit = target.IsAddToQueueMode;

			// fire the IsAddToQueueModeChange event
            if (target.IsAddToQueueModeChange != null)
                target.IsAddToQueueModeChange(target, EventArgs.Empty);
        }
    }

    static void OnOverrideAlbumIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        target.UpdateFullUiAsync();
    }

    static void OnOverrideArtistNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        target.UpdateFullUiAsync();
    }

    static void OnOverrideAlbumNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        target.UpdateFullUiAsync();
    }

    static void OnOverrideAlbumArtPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (MusicController)d;
        target.UpdateFullUiAsync();
    }

    bool _updateFullUiPending;

    void UpdateFullUiAsync()
    {
        if (!_updateFullUiPending)
        {
            _updateFullUiPending = true;
            Dispatcher.BeginInvoke(UpdateFullUi);
        }
    }

    void OnIsNowPlayingModePropertyChanged()
    {
        if (_layoutRoot == null) // i.e. if OnApplyTemplate wasn't called yet
            return;

        Visibility visibleInNowPlayingMode =
            IsNowPlayingMode ? Visibility.Visible : Visibility.Collapsed;
        Visibility collapseInNowPlayingMode =
            IsNowPlayingMode ? Visibility.Collapsed : Visibility.Visible;

        _viewNowPlayingAlbumButton.Visibility = collapseInNowPlayingMode;
        _viewThisAlbumButton.Visibility = visibleInNowPlayingMode;
        _addToQueueButton.Visibility = collapseInNowPlayingMode;
    }

    void UpdateVisualState(bool useTransitions)
    {
        switch (_loadState)
        {
        case LoadState.WaitingForServerAvailability:
            VisualStateManager.GoToState(this, MusicServerNotAvailableName, useTransitions);
            break;
        case LoadState.Loading:
            VisualStateManager.GoToState(this, LoadingStateName, useTransitions);
            break;
        default:
            VisualStateManager.GoToState(this, IdleStateName, useTransitions);
            break;
        }

        if (IsAddToQueueMode)
            VisualStateManager.GoToState(this, AddToQueueName, useTransitions);
        else
        if (!CanControlDisplayedAlbum)
            VisualStateManager.GoToState(this, NoMediaControlsName, useTransitions);
        else
            VisualStateManager.GoToState(this, ShowMediaControlsName, useTransitions);

        if (CanControlDisplayedAlbum)
            VisualStateManager.GoToState(this, ShowTrackInfoName, useTransitions);
        else
            VisualStateManager.GoToState(this, HideTrackInfoName, useTransitions);
    }

	// IControlHelperConnector implementation

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }

    // nested types...

    // based on MusicServiceStatus
    public class BasicStatus
    {
        public PlayState PlayState;
        public string AlbumId;
        public int TrackNumber;
        public double Position;
        public int QueueIndex;

        public void CopyTo(BasicStatus other)
        { 
            other.PlayState = PlayState;
			other.AlbumId = AlbumId;
			other.TrackNumber = TrackNumber;
			other.Position = Position;
			other.QueueIndex = QueueIndex;
        }
    }

    // based on MusicServiceDetailedStatus
    public class DetailedStatus : BasicStatus
    {
		/// <summary>
		/// If <n>true</n>, then as far as we know this <r>DetailedStatus</r> accurately reflects
		/// the state of the music server.  If <n>false</n>, then we've received information (e.g.
		/// <n>BasicStatus</n> that indicates that, for example, the server has a different
		/// "current track", but we don't have all the information for that track yet.
		/// </summary>
		public bool IsCurrentDetailedStatus;

        public string ArtistName; // null or empty if PlayState == NoCurrentTrack
        public string AlbumName; // null or empty if PlayState == NoCurrentTrack
        public byte[] AlbumArt; // null if PlayState == NoCurrentTrack

        public string TrackName; // null or empty if PlayState == NoCurrentTrack
        public double TrackDuration; // 0 if PlayState == NoCurrentTrack
        public int TrackYear; // 0 if PlayState == NoCurrentTrack
        public string TrackComposer; // null or empty if PlayState == NoCurrentTrack

        public int QueuedTrackCount;
        public double QueuedTrackDuration;

        public int Volume; // 0 to 100 -- this is Media Player local volume, not system volume
        public bool IsMuted;
        public double PlaybackRate; // see IWMPSettings::rate; 1.0 means "normal rate"
        public bool IsLoopMode;
        public bool IsShuffleMode;
    }

    public enum PlayState
    {
        NoCurrentTrack = 0, // must be the default value
        Stopped,
        Paused,
        Playing,
        ScanForward,
        ScanReverse
    }

    public enum LoadState
    {
        /// <summary>
        /// The control is waiting for the music server to become available before the control can
        /// begin loading data from the music server.
        /// </summary>
        WaitingForServerAvailability,

        /// <summary>
        /// The control has made a request to the music server to begin loading data, and the
        /// control is now waiting for that request to complete.
        /// </summary>
        Loading,

        /// <summary>
        /// The data has been loaded from the music server, or an error occurred during loading
        /// but we don't want to retry.
        /// </summary>
        Done
    }

    public void Dispose()
    {
        if (_recipientId != Guid.Empty)
        {
            var syncClient = ClientGlobals.GetSyncClient(this) as SyncClient;
            syncClient.UnregisterParcelRecipient(_recipientId);
            _recipientId = Guid.Empty;
        }

		if (_syncPath != null)
		{
			_syncPath = null;
			OnSyncPathSet();
		}
    }
}

}

